#include "TheApp.hpp"
#include "cybertron/core/UtilUrlRequest.hpp"
#include "cybertron/core/UtilFile.hpp"
#include "cybertron/core/JsonReader.hpp"
#include "MasterClient.hpp"
#include"WorkNodeManger.hpp"
#include "cybertron/core/Log.hpp"
#include "cybertron/core/SimOneEnv.hpp"
#include "cybertron/core/UtilPath.hpp"
#include "cybertron/core/Thread.hpp"
#include "cybertron/core/UtilConsole.hpp"
#include "cybertron/core/UtilCRT.hpp"
#include "cybertron/node/StartupArguments.hpp"
#include "cybertron/core/CoreVersion.hpp"
#include "cybertron/core/SimOneEnv.hpp"
#include "cybertron/core/UtilUuidxx.hpp"
#include "cybertron/core/UtilStateHelper.hpp"
#include "cybertron/core/UtilDirectory.hpp"
#include "cybertron/core/UtilStateHelper.hpp"
#include "cybertron/license/DongleInfo.hpp"
#include <iostream>
#include <fstream>
#include <chrono>
#ifdef CYBERTRON_LINUX
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#endif
// determined by the EnvVar LOG_CONSOLE outside on the windows
//#if defined(CYBERTRON_WIN) && defined(NDEBUG)
//#pragma comment(linker, "/subsystem:\"windows\" /entry:\"mainCRTStartup\"" )
//#endif

using namespace cybertron;

static UtilCRTAutoInitialize sCRT;

#ifdef CYBERTRON_LINUX
bool gRunning = true;
void sigterm_handler(int no) {
	gRunning = false;
	logInfo("recv sigterm_handler exit....%d", no);
}
#endif

std::string TheApp::randomGuid() {
	std::string guidString;
	UtilUuidxx::uuid uuid = UtilUuidxx::uuid::Generate();
	guidString = uuid.ToString();
	return guidString;
}

void TheApp::masterClientThread(/*int argc, char* argv[]*/)
{
#ifdef CYBERTRON_LINUX
	signal(SIGTERM, sigterm_handler);
#endif

	mMasterClient = std::make_shared<MasterClient>();
	if (!mMasterClient->initialize(/*argc, argv*/))
	{
		return;
	}
	std::chrono::high_resolution_clock::time_point t1 = std::chrono::high_resolution_clock::now();
	for (;;)
	{
#ifdef CYBERTRON_LINUX
		if (!gRunning) {
			logInfo("SIGTERM Exit.........");
			break;
		}
#endif
		if (!mMasterClient->isRunning()) {
			logInfo("CybertronAgent Exit.........");
			break;
		}
		std::chrono::high_resolution_clock::time_point t2 = std::chrono::high_resolution_clock::now();
		std::chrono::nanoseconds span = (t2 - t1);
		int ms = (int)(span.count() / 1000000);
		t1 = t2;

		mMasterClient->onTimer(ms);

		std::this_thread::sleep_for(std::chrono::milliseconds(1));
	}
}

TheApp::TheApp()
{
	cybertron::setupLogFile("Agent");
	UtilConsole::setTitle("Agent");
	UtilConsole::setColor(EConsoleColor_White, EConsoleColor_Black);

	BaseApp::mInstance = this;

	mAuthCode = "-1";
	mMasterIP = "127.0.0.1";
	mMasterPort = 4500;
	mListenPort = 5001;
	mWebIP = "127.0.0.1";
	mWebPort = "8088";
	mbCfgFromFile = false;
	mbIsService = false;
	mAgentState = AgentState_None;

#ifdef CYBERTRON_WIN
	mConfPath = "c:/users/public/SimOneOS.conf";
#else
	std::string home = getenv("HOME");
	UtilDirectory::createMultipleDirectory(home+"/.simone");
	mConfPath = home+"/.simone/SimOneOS.conf";
#endif

	string AgentPath = UtilPath::getExecutablePath();
	mCommonDllPath = UtilPath::combine(AgentPath, "../../Common/ShareLib/");
	mPythonLibPath = UtilPath::combine(AgentPath, "../../Tools/Python36/Lib/site-packages/");


}


int TheApp::parseArguments(int argc, char* argv[])
{
	for (int i = 1; i < argc; ++i)
	{
		logSilent("Command Argument %d: %s", i, argv[i]);
	}

	for (int i = 1; i < argc; ++i)
	{
		std::string arg = argv[i];
		if (arg == "--mode")
		{

			++i;
			if (i < argc)
			{
				mMode = argv[i];
			}
		}
		if (arg == "--port")
		{
			++i;
			if (i < argc)
			{
				mListenPort = atoi(argv[i]);
			}
		}
		if (arg == "--f")
		{
			++i;
			if (i < argc)
			{
				mbCfgFromFile = true;
				mCfgFilePath = argv[i];
			}
		}
		if (arg == "--webip")
		{
			++i;
			if (i < argc)
			{
				mWebIP = (argv[i]);
			}
		}
		if (arg == "--webport")
		{
			++i;
			if (i < argc)
			{
				//mWebPort = atoi(argv[i]);
				mWebPort = argv[i];
			}
		}
		if (arg == "--auth")
		{
			++i;
			if (i < argc)
			{
				mAuthCode = (argv[i]);
			}
		}

		if (arg == "--masterip")
		{
			++i;
			if (i < argc)
			{
				mMasterIP = (argv[i]);
			}
		}
		if (arg == "--masterport")
		{
			++i;
			if (i < argc)
			{
				mMasterPort = atoi((argv[i]));
			}
		}
		if (arg == "--publicip")
		{
			++i;
			if (i < argc)
			{
				mAgentPublicIP = (argv[i]);
			}
		}
		if (arg == "--info")
		{
			//++i;
			//if (i < argc)
			//{
			//	mAgentPublicIP = (argv[i]);
			//}
			logInfo("Channel name :%s Dongle Setting:%d, admin pin:%s", CHANNEL_NAME, DONGLE_SETTING, ADMINPIN);
			return -1;
		}
		if (arg == "--service")
		{
			mbIsService = true;
		}


	}
	return 0;
}

void TheApp::getVerion()
{
	std::string version = getVersionOnCore("CybertronAgent", "2021622162931");
	printf("%s", version.c_str());
}


int TheApp::beforeRun()
{
	if (!WorkNodeManger::GetInstance().InitWorkPath()) {
		return -1;
	}
	int ret = 0;
	if (SimOneEnv::GetInstance().IsCloudEnv()) {
		ret = InitBeforeRunOnCloud();
	}
	else {
		ret = InitBeforeRunOnLocal();
	}

	return ret;
}
int TheApp::InitBeforeRunOnLocal() {
	if (!CheckLocalSystemAuthority()) {
		logError("Check Authority Failed");
		return -1;
	}

	string agentId;
	if (ReadAgentIdFromLocal(agentId)) {
		mAppInfo.mAppId = agentId;
		mAgentState = AgentState_Ready;
		logInfo("Read Auth Code From Local AgentId:%s", mAppInfo.mAppId.c_str());
	}
	return 0;
}
int TheApp::InitBeforeRunOnCloud() {
	SimOneEnv::SimOneCloudEnvInfo info;
	SimOneEnv::GetInstance().GetCloundEnvInfo(info);
	mAppInfo.mAppId = info.PodNameSpace;
	mAppInfo.mAppId += "_";
	mAppInfo.mAppId += info.PodName;
	mAppInfo.mAppId += "_";
	mAppInfo.mAppId += info.ContainerName;
	mAgentPublicIP = info.PodIP;
	//mMasterIP = "127.0.0.1";
	//mMasterPort = 4500;
	mAgentState = AgentState_Running;
	logInfo("Master Server :%s:%d,Agent Info :%s  Public IP :%s", mMasterIP.c_str(), mMasterPort, mAppInfo.mAppId.c_str(), mAgentPublicIP.c_str());
	std::vector<std::string> capList;
	SimOneEnv::GetInstance().GetWorkCapList(capList);
	int workBaseId = 60001;
	if (!SimOneEnv::GetInstance().GetBaseWorkId(workBaseId)) {
		workBaseId = 60001;
	}
	GetWorkTypeNameMapConfig(mWebIP, mWebPort);
	GetScalableInfoConfig(mWebIP, mWebPort);
	for (size_t i = 0; i < capList.size(); i++)
	{

		Common::EWorkNodeType workType = UtilStateHelper::GetInstance().GetWorkNodeType(capList[i]);
		string Path = UtilStateHelper::GetInstance().GetWorkNodePath(workType);

		workBaseId += 1;
		string strWorkBaseId = std::to_string(workBaseId);
		string commandline = UtilStateHelper::GetInstance().GetWorkNodeCommandLine(workType);

		WorkNodeManger::GetInstance().AddWorkNodeInfo(strWorkBaseId, (Common::EWorkNodeType)workType, Path, commandline, strWorkBaseId, "Cloud");
	}
	return 0;
}
bool TheApp::LoadAgentCfg(string cfgFilePath) {

	if (!UtilFile::exist(cfgFilePath))
	{
		logError("LoadAgentCfg Config file %s doesn't exist.", cfgFilePath.c_str());
		return false;
	}
	cybertron::json jsonNode;
	if (!JsonReader::loadFile(jsonNode, cfgFilePath))
	{
		logError("json load  file error: %s", cfgFilePath.c_str());
		return false;
	}
	string content = jsonNode.dump();
	string version = JsonReader::getString(jsonNode, "version");
	mAuthCode = JsonReader::getString(jsonNode, "authcode");

	auto data = JsonReader::getChild(jsonNode, "webserver");
	mWebIP = JsonReader::getString(data, "webip");
	int  webPort = JsonReader::getInt(data, "webport");
	mWebPort= JsonReader::getString(data, "webport");
	logInfo("Agent.cgf:[AuthCode:%s WebIP:%s WebPort]", mAuthCode.c_str(), mWebIP.c_str(), mWebPort.c_str());
	if (mWebPort.size() == 0) {
		int  webPort = JsonReader::getInt(data, "webport");
		mWebPort = std::to_string(webPort);
		if (mWebPort == "0") {
			mWebPort = "";
		}
	}
	return true;
}

bool TheApp::CheckLocalSystemAuthority() {

	std::string guidString;

	std::ifstream osConfReader(mConfPath);
	if (!osConfReader.is_open())
	{
		std::ofstream osConfWriter(mConfPath, std::ios::trunc);
		osConfWriter.close();
	}
	else {
		osConfReader.close();
	}
	bool bret = UtilFile::exist(mConfPath);
	return bret;
}

bool TheApp::ReadAgentIdFromLocal(string& agentId) {

	std::ifstream osConfReader(mConfPath);
	if (osConfReader.is_open())
	{
		osConfReader >> agentId;
		osConfReader.close();
	}
	else {
		agentId = "";

	}
	if (agentId.length() == 0) {
		return false;
	}

	return true;
}

bool TheApp::SaveAgentIdToLocal (string agentId) {
	std::string guidString;
	std::ifstream osConfReader(mConfPath);
	if (!osConfReader.is_open())
	{
		logError("SaveAgentIdToLocal Failed");
		return false;

	}

	std::ofstream osConfWriter(mConfPath, std::ios::trunc);
	osConfWriter << agentId;
	osConfWriter.close();
	mAppInfo.mAppId = agentId;
	return true;
}


bool TheApp::PitAgentFromWeb(string hostIP, string hostPort, string authCode) {
	string url;
	if (hostPort.empty()) {
		url = "http://" + hostIP + "/api/agent/pit";
	}
	else {
		url = "http://" + hostIP + ":" +hostPort + "/api/agent/pit";
	}

	string postData = "{\"data\": {\"id\": \"" + authCode + "\"}}";
	logInfo("[http]===>%s, body:%s", url.c_str(), postData.c_str());
	try
	{
		UtilUrlRequest::THeaders headers;
		headers["Content-Type"] = "application/json";
		std::string ret = UtilUrlRequest::PostUrl(url, postData, headers);
		cybertron::json jsonReturn;
		if (!JsonReader::loadString(jsonReturn, ret))
		{
			logError("PitAgentFromWeb JSON LoadString Failed");
			return false;
		}
		else
		{
			int code = JsonReader::getInt(jsonReturn, "code");
			if (code == 0) {
				return true;

			}
			else if (code == -2) {
				logError("PitAgentFromWeb Code :%d, authCode:%s already occupyed", code, authCode.c_str());
				return false;
			}else {
				std::string msg = JsonReader::getString(jsonReturn, "msg");
				logError("PitAgentFromWeb Code :%d, Message:%s",code, msg.c_str());

				return false;
			}
		}
		logInfo("[http]<===", url.c_str());
		//logInfo("geturl result:%s", ret.c_str());
	}
	catch (const UtilUrlRequest::TException& ex)
	{
		const char* error = ex.what();
		logInfo("PitAgentFromWeb Catch:%s", error);

		return false;
	}
	return false;
}

AgentConnectWebState TheApp::GetAgentConfigFromWeb(string hostIP, string hostPort, string agentId,
	string& publicIP, string& masterIP, int& masterPort) {
	string url;
	if (hostPort.empty()) {
		url = "http://" + hostIP + "/api/agent/agentConfigInfo?id=" + agentId;
	}
	else {
		url = "http://" + hostIP + ":" + hostPort + "/api/agent/agentConfigInfo?id=" + agentId;
	}
	logInfo("[http]===>%s", url.c_str());
	try
	{
		UtilUrlRequest::THeaders headers;
		headers["Content-Type"] = "application/json";
		std::string ret = UtilUrlRequest::GetUrl(url, headers);
		cybertron::json jsonReturn;
		if (!JsonReader::loadString(jsonReturn, ret))
		{
			logError("GetAgentConfigFromWeb JSON LoadString Failed");
			return AgentConnectWebState_Falied;
		}
		else
		{
			int code = JsonReader::getInt(jsonReturn, "code");
			if (code == 0) {
				auto data = JsonReader::getChild(jsonReturn, "data");
				publicIP = JsonReader::getString(data, "ip");

				logInfo("Agent PublicIP:%s", mAgentPublicIP.c_str());
				if (mMasterIP == "127.0.0.1") {
					masterIP = JsonReader::getString(data, "masterIp");
				}
				masterPort = JsonReader::getInt(data, "masterPort");


				auto workList = JsonReader::getChild(data, "workers");
				WorkNodeManger::GetInstance().InitWorkNodeInfoMap();
				for (int i = 0; i < workList.size(); ++i)
				{
					auto& item = workList[i];
					string workNodeId = JsonReader::getString(item, "id");
					string name = JsonReader::getString(item, "name");
					string alias = JsonReader::getString(item, "alias");
					if (alias == "CybertronUeCpuSensor") {
						continue;
					}
					string commandline = JsonReader::getString(item, "commandline");
					Common::EWorkNodeType  workType = (Common::EWorkNodeType)JsonReader::getInt(item, "type");
					string agentId = JsonReader::getString(item, "agentId");

					string Path = UtilStateHelper::GetInstance().GetWorkNodePath(workType);
					
					WorkNodeManger::GetInstance().AddWorkNodeInfo(workNodeId, (Common::EWorkNodeType)workType, Path, commandline, workNodeId, "web");

				}
			}
			else {
				logError("GetAgentConfigFromWeb Code :%d", code);
				return AgentConnectWebState_Falied;
			}
		}
	}
	catch (const UtilUrlRequest::TException& ex)
	{
		const char* error = ex.what();
		logInfo("GetAgentConfigFromWeb Catch result:%s", error);

		return AgentConnectWebState_Timeout;
	}
	logInfo("[http]<===%s", url.c_str());
	return AgentConnectWebState_Successful;
}
bool TheApp::GetWorkTypeNameMapConfig(string hostIP, string hostPort) {
	string url;
	if (hostPort.empty()) {
		url = "http://" + hostIP + "/api/worker/metadata";
	}
	else {
		url = "http://" + hostIP + ":" + hostPort + "/api/worker/metadata";
	}
	logInfo("[http]===>%s", url.c_str());
	try
	{
		UtilUrlRequest::THeaders headers;
		headers["Content-Type"] = "application/json";
		std::string ret = UtilUrlRequest::GetUrl(url, headers);

		UtilStateHelper::GetInstance().InitNameTypeMap(ret);
	}
	catch (const UtilUrlRequest::TException& ex)
	{
		const char* error = ex.what();
		logInfo("GetAgentConfigFromWeb Catch result:%s", error);

		return false;
	}
	logInfo("[http]<===%s", url.c_str());
	return true;
}

bool TheApp::GetScalableInfoConfig(string hostIP, string hostPort) {
	string url;
	if (hostPort.empty()) {
		url = "http://" + hostIP + "/api/worker/scale";
	}
	else {
		url = "http://" + hostIP + ":" + hostPort + "/api/worker/scale";
	}
	logInfo("[http]===>%s", url.c_str());
	try
	{
		UtilUrlRequest::THeaders headers;
		headers["Content-Type"] = "application/json";
		std::string ret = UtilUrlRequest::GetUrl(url, headers);

		UtilStateHelper::GetInstance().InitScalableTypeMap(ret);
	}
	catch (const UtilUrlRequest::TException& ex)
	{
		const char* error = ex.what();
		logInfo("GetAgentConfigFromWeb Catch result:%s", error);

		return false;
	}
	logInfo("[http]<===%s", url.c_str());
	return true;
}
int TheApp::run()
{
	if (mbCfgFromFile) {
		LoadAgentCfg(mCfgFilePath);
	}
	std::thread thread1(&TheApp::serverThread, this);
	thread1.detach();
	std::this_thread::sleep_for(std::chrono::seconds(3));

	bool isRunning = true;
	string masterIP;
	int masterPort;
	while (isRunning)
	{
		switch (mAgentState)
		{
		case AgentState_None:
			{
				mAuthStatus = PitAgentFromWeb(mWebIP, mWebPort, mAuthCode);
				if (!mAuthStatus) {
					break;
				}
				SaveAgentIdToLocal(mAuthCode);
				logInfo("AgentId:%s", mAppInfo.mAppId.c_str());
				mAppInfo.mAppId = mAuthCode;
				mAgentState = AgentState_Ready;

			}
			break;
		case AgentState_Ready:
			//if (mbCfgFromFile) {
			//	LoadAgentCfg(mCfgFilePath);
			//}
			if (mAuthCode != mAppInfo.mAppId) {
				logWarning("Local Agent ID:%s Not Equal AuthCode:%s", mAppInfo.mAppId.c_str(), mAuthCode.c_str());
			}
			if (mMode != "debug") {
				if (!GetWorkTypeNameMapConfig(mWebIP, mWebPort)) {
					logError("GetWorkTypeNameMapConfig Failed");
					break;
				}
			}
			if (!GetScalableInfoConfig(mWebIP, mWebPort)) {
				logError("GetWorkTypeNameMapConfig Failed");
				break;
			}



			mLoginStatus = GetAgentConfigFromWeb(mWebIP, mWebPort, mAppInfo.mAppId, mAgentPublicIP, masterIP, masterPort);
			if (mLoginStatus == AgentConnectWebState_Successful) {
				if (mMasterIP == "127.0.0.1") {
					mMasterIP = masterIP;
				}
				mMasterPort = masterPort;
				mAgentState = AgentState_Init;
			}
			//mMasterIP = "10.66.9.228";
			EventHandlerTrayConnection::GetInstance().setAuthCode(mAuthCode);
			EventHandlerTrayConnection::GetInstance().setAuthStatus(mAuthStatus);
			EventHandlerTrayConnection::GetInstance().setLoginStatus(mLoginStatus);
			break;
		case AgentState_Init:
			{
				mAgentState = AgentState_Running;
			}

			break;
		case AgentState_Running:
			isRunning = false;
			break;
		default:
			break;
		}
		std::this_thread::sleep_for(std::chrono::milliseconds(1000));
	}
	std::thread thread2(&TheApp::masterClientThread, this);
	thread2.detach();

	UtilCRT::call("runDaemon", [this]() {
		this->mainThreadRun();
	});

	return 0;
}
void TheApp::serverThread() {
	logInfo("Agent Server Start(Listen Port:%d)", mListenPort);
	mAgentServer = std::make_shared<AgentServer>();
	mAgentServer->listen(mListenPort);
	mAgentServer->run();
}
void TheApp::mainThreadRun() {
	long long lastStamp = 0;

	while (true)
	{
		auto now = std::chrono::high_resolution_clock::now();
		std::chrono::nanoseconds createTime = now.time_since_epoch();
		std::chrono::milliseconds createTimeMS((createTime.count() / 1000000));

		checkWorkList();

		std::this_thread::sleep_for(std::chrono::milliseconds(1000));

		if (createTimeMS.count() - lastStamp > 30 * 1000) {

			lastStamp = createTimeMS.count();
		}
	}
}

void TheApp::checkWorkList() {
	WorkNodeManger::GetInstance().checkWorkNodeState();
	std::this_thread::sleep_for(std::chrono::seconds(1));
}

//////////////////////////
void TheApp::workActiveStopTask(string taskId, string workNodeId, Common::EWorkNodeType workType, Common::ETaskEndReason reason) {
	mMasterClient->Send2MasterStopTask("CheckWorkStateThread",taskId, workNodeId, workType, reason);
}

void TheApp::refreshWorkNodeList() {
	GetAgentConfigFromWeb(mWebIP, mWebPort, mAppInfo.mAppId, mAgentPublicIP, mMasterIP, mMasterPort);
	GetWorkTypeNameMapConfig(mWebIP, mWebPort);
	GetScalableInfoConfig(mWebIP, mWebPort);
	WorkNodeManger::GetInstance().SyncWorkNodeListToMaster();
}
